// GrammarIntelliForJs.js

function GIMatchCollection() {
    this.Matches = new Array();         // Array of GIMatch
    this.NamedMatches = new Array();    // Associative array that returns a GIMatch based on name
    this.Add = function(giMatch) {      // We only collect non-literal match results
        if (giMatch != null && giMatch.MatchingState != null && !giMatch.MatchingState.IsLiteral) {
            this.Matches[this.Matches.length] = giMatch;
            if (this.NamedMatches[giMatch.MatchingState.Name]) {
                // Convert the result into array
                throw "Error: Multiple matches found against named pattern '" + giMatch.MatchingState.Name + "'";
            } else {
                this.NamedMatches[giMatch.MatchingState.Name] = giMatch;
            }
        }
    }

    this.ToXML = function() {
        var sOutputXML = "<GIMatchCollection>", i = 0;
        for (i = 0; i < this.Matches.length; i++) {
            sOutputXML += this.Matches[i].ToXML();
        }

        sOutputXML += "</GIMatchCollection>"

        return sOutputXML;
    }

    return this;
}

function GIMatch(matchedValue, oGIMatchingState) {
    this.Value = matchedValue;
    this.MatchingState = oGIMatchingState;
    this.ToXML = function() {
        var sOutputXML = "<GIMatch>";
        sOutputXML += GIM_XMLSimpleNodeString("Value", this.Value);
        sOutputXML += this.MatchingState.ToXML();
        sOutputXML += "</GIMatch>";

        return sOutputXML;
    }

    return this;
}

function GIMachineWordState(sWordRegex, isLiteral, iStateID, sStateName, isFinal) {
    this.Pattern = sWordRegex;
    this.IsLiteral = isLiteral;
    this.StateID = iStateID;
    this.Name = sStateName;
    this.IsAcceptable = isFinal;
    this.IsCaseSensitive = false;
    this.NextStates = new Array(); // Next possible states of type GIMachineWordState
    this.ToXML = function() {
        var sOutputXML = "<GIMWordState>", i = 0;
        sOutputXML += GIM_XMLSimpleNodeString("ID", this.StateID);
        sOutputXML += GIM_XMLSimpleNodeString("Name", this.Name);
        sOutputXML += GIM_XMLSimpleNodeString("Pattern", this.Pattern);
        sOutputXML += GIM_XMLSimpleNodeString("IsAcceptable", this.IsAcceptable);
        sOutputXML += GIM_XMLSimpleNodeString("IsLiteral", this.IsLiteral);
        sOutputXML += GIM_XMLSimpleNodeString("IsCaseSensitive", this.IsCaseSensitive);
        
        for (i = 0; this.NextStates != null && i < this.NextStates.length; i++) {
            sOutputXML += this.NextStates[i].ToXML();
        }
        sOutputXML += "</GIMWordState>";

        return sOutputXML;
    }

    this.Match = function(sInput) { // returns a GIMatch object or null
        var oResult = null;
        if (this.IsLiteral) {   // Literal text comparison
            if (this.IsCaseSensitive) {
                if (this.Pattern == sInput) {
                    oResult = new GIMatch(sInput, this);
                    return oResult;
                }
            } else {
                if (this.Pattern.toLowerCase() == sInput.toLowerCase()) {
                    oResult = new GIMatch(sInput, this);
                    return oResult;
                }
            }
        } else {                // Regex based comparison
            var re = new RegExp(this.Pattern);
            if (sInput.match(re)) {
                oResult = new GIMatch(sInput, this);
                return oResult;
            }
        }

        return null;
    }

    this.IsAMatch = function(sInput) {
        if (this.IsLiteral) {   // Literal text comparison
            if (this.IsCaseSensitive) return (this.Pattern == sInput); else
                return (this.Pattern.toLowerCase() == sInput.toLowerCase());
        } else {                // Regex based comparison
            var re = new RegExp(this.Pattern);
            return (sInput.match(re)) ? true : false;
        }

        return false; // this line will never be reached !
    } // end of IsAMatch

    this.IsPrefix = function(sInput) {
        if (this.IsLiteral && sInput != null && sInput.length > 0) {
            var iLen = sInput.length;
            if (this.IsCaseSensitive) return (this.Pattern.substring(0, iLen) == sInput); else
                return (this.Pattern.toLowerCase().substring(0, iLen) == sInput.toLowerCase());
        }

        return false;
    }

    return this;
}

function GIMachine(sMachineName) {
    this.Name = sMachineName;
    this.States = new Array(); // array of GIMachineWordState
    this.ToXML = function() {   
        var sOutputXML = "<GIMachine>", i = 0;
        sOutputXML += GIM_XMLSimpleNodeString("Name", this.Name);
        for (i = 0; this.States != null && i < this.States.length; i++) {
            sOutputXML += this.States[i].ToXML();
        }
        sOutputXML += "</GIMachine>";
        return sOutputXML;
    }
    this.WriteTrace = function(sMsgType, sMsg, oTracerBox) {
        if (oTracerBox != null) {
            oTracerBox.value += "[" + sMsgType + "] : " + sMsg + "\r\n";
        }
    }

    this.Suggestions = function(sPartialInput, oTracerBox) {    // TracerBox is optional, when provided we dump log in it.Returns a collection of next possible words/pattern, if input has partial word then the possible word is included
        var arrResult = new Array(), arrWordsTemp = sPartialInput.split(/[\s\t]/), i = 0, sWord = "", currentStatesToCheck = this.States, arrWords = new Array();
        this.WriteTrace("INFO", "inside suggestions", oTracerBox);
        this.WriteTrace("INFO", "split length = " + arrWords.length, oTracerBox);
        for (i = 0; arrWordsTemp != null && arrWordsTemp.length > 0 && i < arrWordsTemp.length; i++) { // get rid of empty/blank/null words... can happen due to multiple whitspaces
            sWord = arrWordsTemp[i];
            if (sWord != null && sWord.length > 0) {
                arrWords[arrWords.length] = sWord;
            }
        }

        for (i = 0; arrWords != null && arrWords.length > 0 && i < arrWords.length; i++) {
            sWord = arrWords[i];
            if (sWord != null && sWord.length > 0) {
                this.WriteTrace("INFO", "checking presence for '" + sWord + "'", oTracerBox);
                var oPossibleState = this.GetFirstMatchingState(sWord, currentStatesToCheck, oTracerBox);
                if (oPossibleState != null) {
                    this.WriteTrace("INFO", "found exact matching state ", oTracerBox);
                    if (i == (arrWords.length - 1)) return oPossibleState.NextStates;
                    else { // jump to next states
                        currentStatesToCheck = oPossibleState.NextStates;
                    }
                }
                else {  // No Matching ... check if its a partial word
                    this.WriteTrace("INFO", "no matching state found ... check for partial matches...", oTracerBox);
                    oPossibleState = this.GetFirstLiteralStateForPrefix(sWord, currentStatesToCheck, oTracerBox);
                    if (oPossibleState != null) {
                        this.WriteTrace("INFO", "found partial match for state " + oPossibleState.Pattern + " index = " + i + ", Count = " + arrWords.length, oTracerBox);
                        if (i == (arrWords.length - 1)) {
                            var arrStates = new Array();
                            arrStates[0] = oPossibleState;
                            return arrStates;
                        }
                        else { // jump to next states
                            return null;
                        }
                    } else {
                        return null;
                    }
                }
            } // if non-empty word
        } // end of for

        return arrResult;
    }                      // end of Suggestions

    this.GetFirstMatchResult = function(sWord, arrStatesToCheck, oTracerBox) {
        var oResult = null, i = 0;
        if (sWord != null && sWord.length > 0 && arrStatesToCheck != null && arrStatesToCheck.length > 0) {
            while (i < arrStatesToCheck.length && oResult == null) {
                oResult = arrStatesToCheck[i].Match(sWord);
                i++;
            } //end of while
        }

        return oResult;
    }

    this.GetFirstMatchingState = function(sWord, arrStatesToCheck, oTracerBox) {
        var oResult = null, i = 0;
        if (sWord != null && sWord.length > 0 && arrStatesToCheck != null && arrStatesToCheck.length > 0) {
            while (i < arrStatesToCheck.length && oResult == null) {
                if (arrStatesToCheck[i].IsAMatch(sWord)) {
                    oResult = arrStatesToCheck[i];
                }
                i++;
            }//end of while
        }

        return oResult;
    }

    this.GetFirstLiteralStateForPrefix = function(sWord, arrStatesToCheck, oTracerBox) {
        var oResult = null, i = 0;
        if (sWord != null && sWord.length > 0 && arrStatesToCheck != null && arrStatesToCheck.length > 0) {
            while (i < arrStatesToCheck.length && oResult == null) {
                if (arrStatesToCheck[i].IsPrefix(sWord)) {
                    oResult = arrStatesToCheck[i];
                }
                i++;
            } //end of while
        }
        return oResult;
    }

    this.ProcessText = function(sInput, oTracerBox) {           // Returns a collection of GIMatch objects
        var arrResult = null, arrWordsTemp = sInput.split(/[\s\t]/), i = 0, sWord = "", currentStatesToCheck = this.States, arrWords = new Array();
        this.WriteTrace("INFO", "inside ProcessText", oTracerBox);
        for (i = 0; arrWordsTemp != null && arrWordsTemp.length > 0 && i < arrWordsTemp.length; i++) { // get rid of empty/blank/null words... can happen due to multiple whitspaces
            sWord = arrWordsTemp[i];
            if (sWord != null && sWord.length > 0) {
                arrWords[arrWords.length] = sWord;
            }
        }
        this.WriteTrace("INFO", "split length = " + arrWords.length, oTracerBox);
        for (i = 0; arrWords != null && arrWords.length > 0 && i < arrWords.length; i++) {
            sWord = arrWords[i];
            if (sWord != null && sWord.length > 0) {
                this.WriteTrace("INFO", "checking presence for '" + sWord + "'", oTracerBox);
                var oPossibleMatch = this.GetFirstMatchResult(sWord, currentStatesToCheck, oTracerBox);
                if (oPossibleMatch != null) {
                    this.WriteTrace("INFO", "found exact matching state ", oTracerBox);
                    if (arrResult == null) arrResult = new GIMatchCollection();
                    arrResult.Add(oPossibleMatch);
                    currentStatesToCheck = oPossibleMatch.MatchingState.NextStates;
                    if (i == (arrWords.length - 1)) {
                        // we accept the complete result only if the last state is acceptable, this safeguards us against valid but not complete sentences
                        return (oPossibleMatch.MatchingState.IsAcceptable)? arrResult : null;
                    } 
                }
                else {  // No Matching ... 
                    return null;
                }
            } // if non-empty word

        } // end of word loop

        return null;    // most probably this line will not be reached
    }
    
    this.RegisterCallBackByStateID = function(iStateID) {
        // TODO:    
    }

    
    
    return this;
}

function GIM_XMLSimpleNodeString(sNodeName, sNodeValue){
    return "<" + sNodeName + "><![CDATA[" + sNodeValue + "]]></" + sNodeName + ">";
}

function GetLiteralPattern(sLiteralText, iCommandId, markEndAsAcceptableOrLinkToState) {
    var arrWords = sLiteralText.split(/[\s\t]/), i = 0, sWord = "", oStartState = null, oLastState = null;
    if (arrWords != null && arrWords.length > 0) {
        for (i = 0; i < arrWords.length; i++) {
            sWord = arrWords[i];
            if (sWord != null && sWord.length > 0) {
                if (oStartState == null) {  // first literal
                    oStartState = new GIMachineWordState(sWord, true, iCommandId, sWord, false);
                    oLastState = oStartState;
                } else {
                    var index = oLastState.NextStates.length;
                    oLastState.NextStates[index] = new GIMachineWordState(sWord, true, iCommandId, sWord, false);
                    oLastState = oLastState.NextStates[index];
                }
            } // if condition
        } // for loop

        if (typeof (markEndAsAcceptableOrLinkToState) == "boolean") {
            if (markEndAsAcceptableOrLinkToState && oLastState != null) {
                oLastState.IsAcceptable = true;
            }
        } else {
            if(markEndAsAcceptableOrLinkToState != null && oLastState != null){
                oLastState.NextStates[0] = markEndAsAcceptableOrLinkToState;
            }
        }
    } // if text had words

    return oStartState;
}




